package com.zz.springboot.aop;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;

@Aspect
@Component
@Slf4j
public class LogAspect {
    private Map<Class<?>, Logger> loggerMap = new HashMap<>();

    /**
     * 这个项目中，不建议进一步代理到Manager层去，因为在Manager层涉及对自定义注解的定位
     * 如果aop作用到了Manager层，那么就无法拿到自定义注解标注的方法，因为aop获得的是代理类和代理方法，而注解标注的是真实方法
     */
    @Pointcut("execution(public * com.zz.springboot..*ServiceImpl.*(..))")
    public void aspect() {
    }

    /**
     * @param joinPoint 切入点，即被拦截的方法的execution信息
     * @return
     * @throws Throwable
     */
    @Around("aspect()")
    public Object around(JoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        //1.获得切入点的所在类、方法名、入参
        Signature signature = joinPoint.getSignature(); //execution中的相关方法信息
        String className = signature.getDeclaringTypeName(); //全限定类名
        String methodName = signature.getName(); //方法名
        Object[] args = joinPoint.getArgs();  //入参
        //2.获得切入点所在类中的log，用于打印日志
        Class<?> tClass = joinPoint.getTarget().getClass(); //getTarget():获得被代理的原始对象
        Logger log = loggerMap.get(tClass);
        if (null == log) {
            Field[] fields = tClass.getDeclaredFields(); //获得ServiceImpl类中的所有成员变量，包括private
            Field logFiled = getLogFiled(fields, tClass);
            if (null != logFiled) {
                logFiled.setAccessible(true);
                log = (Logger) logFiled.get(joinPoint.getTarget()); //获得log值
            } else {
                log = this.log;
            }
            loggerMap.put(tClass, log);
        }
        //3.开始
        Object result = null;
        try {
            // 此处开始调用原方法
            result = ((ProceedingJoinPoint) joinPoint).proceed();
        } catch (Throwable e) {  //
            log.error("调用{}.{}发生异常，耗时:{}ms，入参:{}，异常信息:", className, methodName,
                    (System.currentTimeMillis() - start), JSON.toJSON(args), e.getMessage());
            throw e;
        }
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        String inParams = JSON.toJSONString(args);
        String outParams = JSON.toJSONString(result);

        long end = System.currentTimeMillis();
        log.info("调用{}.{}完成，耗时:{}ms，入参:{}，结果:{}", className, methodName, (end - start), inParams, outParams);

        return result;
    }

    /**
     * 获取被aop管理的类中的log值
     *
     * @param fields
     * @param tClass
     * @return
     * @desc 使用@Slf4j 会在编译时自动为类生成一个基于 SLF4J的日志对象 log
     */
    private Field getLogFiled(Field[] fields, Class<?> tClass) {
        for (Field field : fields) {
            if ("org.slf4j.Logger".equals(field.getType().getName())) {
                return field;
            }
        }
        Type supperClass = tClass.getGenericSuperclass();
        if (null != supperClass) {
            return getLogFiled(tClass.getSuperclass().getDeclaredFields(), tClass.getSuperclass());
        }
        return null;
    }

}
